为何killall有时找不到你的进程?killall是干什么的?
来源:公众号【编程珠玑】
作者:守望先生
ID:shouwangxiansheng
前言
在Linux下有很多命令用于杀死进程,它们可以用于不同的场景,例如通过进程名杀死进程,通过pid杀死进程。这些方法我不准备一一列举,本文想说明的一个问题是,为什么明明通过ps找到了进程,但是通过killall却说找不到呢?如果你没有遇到过这样的问题?那你更要注意了!
killall简介
与kill不同的是,killall可以根据进程名来杀死进程,不像kill,可能先需要使用ps(可以参考《ps命令实例详解》)找到进程id,然后发送信号,就像下面这样:
ps -ef|grep hello
root 15530 6335 0 14:55 pts/4 00:00:00 ./hello
kill -9 15530
这样进程就被我们杀死了,我们可以直接使用killall:
$ killall hello
是不是觉得方便多了?
而且由于killall是根据名称杀死进程,因此如果当前运行着大量的hello程序,那么可以一次性杀死所有hello程序。
除此之外,它还有很多参数,例如忽略大小写,根据模式匹配进程名,杀死某个时间的进程等等,这里就不详解介绍了,有兴趣的可以查看man killall手册。
今天这里想要说明的是一种killall失效的情况。
killall失效了?
我写了一个自己的hello程序,然后尝试使用killall杀死正在运行的hello程序。
$ killall hello
hello: no process found
什么?竟然说找不到?一个ps丢过来:
$ ps -ef|grep hello
root 15765 6335 0 15:05 pts/4 00:00:00 ./hello
所以killall你到底行不行?
为何
为了找出killall失效的原因,我们必须知道它到底是如何通过进程名找到进程的。
这个时候就需要祭出我们的神器strace了,看看killall杀死一个普通进程到底做了哪些事情:
$ strace killall hello
open("/proc/100/cmdline", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "", 1024) = 0
close(3) = 0
open("/proc/104/stat", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "104 (ipv6_addrconf) I 2 0 0 0 -1"..., 1024) = 161
close(3) = 0
open("/proc/113/stat", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "113 (kstrp) I 2 0 0 0 -1 6923888"..., 1024) = 153
close(3) = 0
open("/proc/137/stat", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "137 (charger_manager) I 2 0 0 0 "..., 1024) = 163
close(3) = 0
open("/proc/137/cmdline", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "", 1024) = 0
打印结果很多,我只提取了部分,可以看到的是,killall会去proc文件系统(proc文件系统可以参考《Linux中不可错过的信息宝库》)下查找各个进程id下的stat文件和cmdline,stat文件是怎样的呢?我们随便找一个看看:
cat /proc/16131/stat
16131 (hello) S 6335 16131 6335 34820 16131 1077936128 70 0 0 0 0 0 0 0 20 0 1 0 2465163 4321280 194 18446744073709551615 4194304 4196092 140734969955328 0 0 0 0 0 0 1 0 0 17 0 0 0 0 0 0 6295056 6295608 19099648 140734969962804 140734969962812 140734969962812 140734969966576 0
我们不要被这么多内容吓到了,可以明显看到的是,里面有hello啊。至此我们可以猜测,killall命令会去读取进程在proc文件系统中的stat文件里的名字。那么如果这么名字和你要杀死的进程对不上不就找不到了吗?
至此,想必你已经明白前面问题的原因了。
如何给自挖坑
那么怎样修改stat中显示的名字呢?我们可以使用prcl函数,话不多少,直接看示例代码:
//来源:公众号【编程珠玑】
//作者:守望先生
int main(void)
{
prctl(PR_SET_NAME,"bianchengzhuji");
sleep(100);//防止进程立即退出,便于观察
return 0;
}
这个时候再编译运行程序查看stat和status中的名字:
$ gcc -o hello hello.c
$ cat /proc/pid/stat #这里的pid换成示例的进程id
16441 (bianchengzhuji) S 6335 16441 6335 34820 16441 1077936128 69 0 0 0 0 0 0 0 20 0 1 0 2535513 4321280 156 18446744073709551615 4194304 4196188 140724949606512 0 0 0 0 0 0 1 0 0 17 3 0 0 0 0 0 6295056 6295616 31719424 140724949614900 140724949614908 140724949614908 140724949618672 0
$ more /proc/pid/status
Name: bianchengzhuji
Umask: 0002
State: S (sleeping)
Tgid: 16441
Ngid: 0
Pid: 16441
PPid: 6335
是不是发现名字变了呢?虽然进程名还是hello,但是killall已经找不到它了,不过:
$ killall bianchengzhuji
还是可以的。
为什么会出现这种情况呢?
想象一下,你们公司内部不想重复造轮子,搞了一套开发框架,main函数在框架里写好了,通过库的形式给你们使用,可能名字早就定好了。
但是,这里需要特别注意的是,如果名字超过了15个字符,在stat和status文件中看到的将会看到被截断的名字。
玩点刺激的
既然看到这里了,不如再玩点刺激的。
看看下面的代码:
//来源:公众号【编程珠玑】
int main(int argc,char *argv[])
{
strncpy(argv[0],"bianchengzhuji",sizeof("bianchengzhuji"));
sleep(100);
return 0;
}
是不是发现和前面例子的main函数不一样?参考这里(《C语言的main到底该怎么写》)
这个时候你去编译运行:
$ gcc -o hello hello.c
$ ./hello
然后尝试使用ps去查找进程:
$ ps -ef|grep hello
root 17831 17818 0 16:09 pts/26 00:00:00 grep --color=auto hello
然后你就会惊喜的发现找不到hello进程。
但是使用:
$ ps -ef|grep shouwangxiansheng
root 17938 6335 0 16:12 pts/4 00:00:00 shouwangxiansheng
root 17954 17924 0 16:12 pts/27 00:00:00 grep --color=auto shouwangxiansheng
hyb@ub
就可以找到。
这种情况下直接改变了程序的命令名,因此ps之类的找不到。
这个时候看命令名是什么呢?
$ cat /proc/17938/cmdline
shouwangxiansheng
不过这个时候killall还是可以找到它!也就是你可以使用killall hello杀死它。
总结
如果你发现你的程序无法通过killall 进程名的方式杀死的话,不妨看看proc文件系统中这个进程的stat文件或者status文件中的名。
推荐阅读:
关注公众号【编程珠玑】,获取更多Linux/C/C++/算法/计算机基础/工具等原创技术文章。后台免费获取经典电子书和视频资源